6502 Indirect Addressing Modes
The 6502 indexed addressing modes, discussed in last month’s column, are useful for implementing small arrays and for accessing elements of strings in known locations. For large arrays, and for arrays and strings whose address is not known at assembly time, the 6502 indirect indexed addressing modes are required.

So far you’ve met three basic types of addressing modes on the 6502 microprocessor: the immediate, the absolute, and the indexed. (For our purposes, the relative addressing mode, used by the branches, is identical to the absolute mode.) An addressing mode specifies where in memory data to be accessed is located. For example, the immediate mode tells the 6502 that the data is to be found immediately after the instruction’s opcode byte. The absolute mode, rather than providing the data itself, follows the instruction opcode with the address of the data in memory.

The 6502 indirect addressing mode is a logical extension of this sequence. Rather than following the instruction opcode with the data to be accessed or the address of the data, it is followed with the address of the address of the data. Figure 1 pictures how the various addressing modes function; Figure 2 is a closer view of the indirect mode.

  Immediate Addressing Mode:                                               |        |
   First byte  Second byte                                       Location: |        |
   -----------------------                                         ----------
   |  OPCODE  |   DATA   |                                              n  | OPCODE |
   -----------------------                                                 ----------
                                                                           |        |
  Absolute Addressing Mode:                                           n+1  | Address| ----
   First byte  Second byte  Third byte                                     |        |    |
   ------------------------------------                               n+2  |---------    |
   |  OPCODE  |       ADDRESS         |                                    |        | |  |
   ------------------------------------                                    |        |/   |
                      |      |                                             |     ___/ |  |
                      \       \             --------                       |   _/   |/   |
                        \------\----------> | DATA |                       |__/ ___/|    |
                                            --------                      /|___/    |    |
                                                                          /|        |    |

 Indexed Addressing Mode:                 Index Reg                        |--------|    |
    First byte  Second byte  Third byte   ----------                       |        |    |
   ------------------------------------   |        |                  m    | Address| <---
   |  OPCODE  |       ADDRESS         |   ----------                  m+1  |        | ----
   ------------------------------------        |                           |--------|    |
                      |      |                 |                           |        | |  |
                      \       \               \!/       --------           |        |/   |
                        \--------------------> + -----> | DATA |           |     ___/ |  |
                                                        --------           |   _/   |/   |
                                                                           |__/ ___/|    |
  Indirect Addressing Mode:                                               /|___/    |    |
   First byte  Second byte  Third byte                                    /|        |    |
   ------------------------------------                                    |--------|    |
   |  OPCODE  |       ADDRESS         |                                    |  DATA  | <---
   ------------------------------------                                    |--------|
                      |      |                                             |        |
                      --------                                             |        |
                         |
                         |                                         Figure 2.
            First byte  \!/  Second byte                      The Indirect Addressing Mode:
              --------------------------          --------
              |        ADDRESS         | -------> | DATA |
              --------------------------          --------
       Figure 1. Operation of the 6502 addressing modes

The indexed addressing modes on the 6502 add the contents of the X or Y register to the
absolute address that follows the instruction opcode to obtain the effective address.

By applying this same technique to the indirect addressing modes you come up with the
indirect, indexed mode.

When using this mode, the value in the Y register is added to the value pointed at by the
byte following the instruction opcode.

This sum provides the true effective address for the instruction.

See Figure 3.

A second form of indirect addressing—indexed, indirect—is also available.

Here the contents of the X index register are added to the address immediately following
the instruction opcode to obtain the address of the address of the data you're interested in.

The operation of the indexed, indirect mode is shown in Figure 4.

Nasty Reality #1:
The Indirect Jump Instruction
There’s only one problem with the 6502’s indirect addressing modes:
They can only be used with the JMP instruction.
You can’t load, add, subtract, or do anything else with them in their pure form.

The syntax for the indirect JMP instruction is:

      JMP (ADRS)

where ADRS is the address of a two-byte pointer containing the address where you want to jump.

ADRS must point at the low-order byte of the new address, and location ADRS +1 must
contain the high-order byte.

There is a nasty little bug in the 6502 chip that can get you into a lot of trouble if you're unaware. The two-byte address pointed at by ADRS must be totally contained within a single page of memory. If ADRS holds the value xxFF (where xx is any single-byte value), then the 6502 fetches the low-order byte from location xxFF (as you’d expect) and the high-order byte from location xx00. Note that you really wanted the high-order byte fetched from location yy00, where yy =xx+1.
     --------------------                                    ----------------
     | OPCODE | ADDRESS |                                    |OPCODE|ADDRESS|
     --------------------  Y-Index Register                  ----------------
                   |           --------                                 |
                   |           |      |                X-Index Register |
                  \!/          --------                    ---------   \!/       ---------
              -----------          |                       |       | -> + -----> |ADDRESS|
              |         |         \!/                      ---------             ---------
              | ADDRESS | -------> +                                                 |
              |         |          |                                                 |
              -----------         \!/                                               \!/
                               ----------                                         --------
                               |  DATA  |                                         | DATA |
                               ----------                                         --------
      Figure 3. The (IND),Y addressing mode.        Figure 4. The (IND,X) Addressing mode.

Since the purpose of this month’s column is to discuss implementing arrays,
I will leave the discussion of the JMP indirect instruction for later.

Right now let’s worry about implementing large arrays with the indirect, indexed addressing modes.

Nasty Reality #2:
The Zero Page Addressing Mode

Unfortunately,

I must introduce yet another 6502 addressing mode before continuing the discussion of the
indirect mode.

In reality, the indirect mode isn’t just a combination of two modes (indirect and indexed),
but rather three modes: indirect, indexed, and zero page.

The 64K address space of the 6502 is divided into 256 groups of 256 bytes each.

Each block of 256 bytes is called a page, and the pages are numbered sequentially.

Page zero is the first page of memory (addresses $0000-$00FF), page one is the
second page (addresses $0100-$01FF), etc.

The zero page addressing mode gets its name from the fact that it only allows you to
access the first 256 bytes in the 64K address range,

i.e., page zero.

There are two advantages to the zero page mode (compared to the absolute mode):

An instruction using the zero page mode is one byte shorter and one microsecond faster
than the equivalent instruction using the absolute mode.

There are also two big disadvantages:

You can only access the first 256 bytes of memory in the 6502's address space,
and page zero is prime real estate — everyone else wants to use it too.

In particular, DOS, ANIX, Basic, the Apple monitor, Pascal, CP/M (actually the BIOS drivers),
SPEED/ASM, and many other programs that your SPEED/ASM program must co-exist with,
all use some zero page memory.

If you use the same location as SPEED/ASM or DOS, you can make the system crash.

So use page zero only when you have to, and make sure you're not using any zero page
locations occupied by a co-resident system.

Typically locations $50-$6F are available when operating SPEED/ASM under Apple DOS.

Declaring a zero page variable is done quite a bit differently than declaring normal
SPEED/ASM variables.

Rather than using the DFS or ADR pseudo-opcode to reserve space for the zero page variable,
you must use the EPZ, “equate to page zero,” pseudo-upcode.

The syntax for EPZ is:

      <varname> EPZ <address>

                   Jump indirect instruction
                   ---------------------------
                   |     |         |         |
                   | $6C | Two-byte address  |
                   |     |         |         |
                   ---------------------------

                   All (ZPG,X) Instructions
                   ---------------------
                   |        |          |
                   | OPCODE | One-Byte |
                   |        |  Address |
                   ---------------------

                   All (ZPG),Y Instructions
                   ---------------------
                   |        |          |
                   | OPCODE | One-Byte |
                   |        |  Address |
                   ---------------------
               Figure 5. Indirect instruction formats.

To explain, <address> is the actual address in memory where the variable is to be stored.

Since this is a zero page variable, you must make sure that <address> is in the range $00-$FF,
or LISA will signal an error when you attempt to assemble the program.

Since SPEED/ASM variables (and 6502 pointers for that matter) require two bytes,
make sure that both locations <address> and <address> + 1 in page zero are open,
because both will be used by the SPEED/ASM routines and the indirect addressing modes.

Once you’ve “equated” a symbol to a zero page address using the EPZ pseudo-opcode,
you can treat that label exactly like any other SPEED/ASM integer variable.

To initialize a variable declared in page zero, you could use the LOAD or MOVE SPEED/ASM routine.
To access it you could use any valid 6502 instruction (that works with the absolute addressing mode),
or any of the SPEED/ASM integer functions like MUL, DIV, PRTINT, etc.

When using a pure 6502 instruction, you will usually notice that only two bytes of
object code (instead of the normal three) are emitted.

This is because;
    
     the high-order byte of zero is implied by the use of the zero page addressing mode.

Back to the Indirect Addressing Modes
Before I digressed to a discussion of the zero page addressing mode, I mentioned that the indirect, indexed, and the indexed, indirect modes are actually combinations of three modes: indirect, indexed and zero page. All indirect instructions (except jump indirect) are two bytes long. The first byte is the 6502 opcode and the second byte is the zero page address of the pointer to the memory location you’re interested in. See Figure 5.
      STRPTR   EPZ $50
      ;
               JSR LOAD             ; Copy address of STRING
               ADR STRING,STRPRT    ; into STRPTR
      ;
               LDY #0
      PRTLOOP  LDA (STRPTR),Y
               BEQ PRTDONE
               JSR PUTC
               INY
               JMP PRTLOOP
      ;
      PRTDONE:

        Listing 1a.


Note that the program sequences in Listings 1a and 1b perform equivalent functions.

Also note that in the case of the indirect, indexed-by-Y mode you are still limited to
256 bytes due to the Y index register’s 8-bit limitation.

So why

               LDA #0
      PRTLOOP  LDA STRING,Y
               BEQ PRTDONE
               JSR PUTC
               INY
               JMP PRTLOOP
      ;
      PRTDONE:

     Listing 1b.

 use the indirect addressing mode?
 
It doesn’t appear to provide any additional features;

   in fact,

it makes a somewhat complex process (indexed addressing) even more complex.

Its beauty lies in the fact that the pointer can be changed under program control.

For example,

  the Listing 1a program segment (using the indexed mode) is forever limited to printing
  the string STRING.
  
  Listing 1b can be changed to print any string by simply changing the LOAD instruction
  before the print loop.
  
  For example,
      consider the code in Listing 2..

One of three different strings will be printed, depending upon the value loaded into STRPTR.

While this example certainly justifies the existence of the indirect, indexed-by-Y mode,
it still doesn’t show how to access more than 256 bytes using the indirect mode.

And that’s the whole purpose of this month’s column—to describe how to access elements
of an array containing more than 256 bytes.

                JSR LOAD
                ADR STRING1,STRPTR
                JMP PRTIT
                ..  ..
                ..  ..
                ..  ..
                JSR LOAD
                ADR STRING2,STRPTR
                JMP PRTIT
                ..  ..
                ..  ..
                ..  ..
                JSR LOAD
                ADR STRING3,STRPTR
                JMP PRTIT
                ..  ..
                ..  ..
                ..  ..
      PRTIT     LDY #0
      PRTLOOP   LDA (STRPTR),Y
                BEQ PRTDONE
                JSR PUTC
                INY
                JMP PRTLOOP
      ;
      PRTDONE:

                 Listing 2.

      ; Assume SPEED/ASM variable "I" contains the index into the byte array "B".
      ;
            CLC
            LDA I
            ADC #B
            STA ARRAYPTR
            LDA I+1
            ADC /B
            STA ARRAYPTR+1
            LDY #0
            LDA (ARRAYPTR),Y ;Loads B[I] into accumulator.
                           
                 Listing 3.

The secret to accessing large blocks of data using the indirect addressing mode is to
modify the two-byte pointer instead of the Y register.

By setting the Y register to zero (which makes the indirect, indexed-by-Y mode behave
exactly like a true indirect addressing mode) and then incrementing the two-byte
zero page pointer, you can access up to 64K of data (the amount of memory accessible with
a two-byte pointer).

If that’s not enough memory, you're using the wrong microprocessor!

To access an element of a byte array that contains more that 256 elements,
the address of the desired element can be computed by the formula:

      <adrs> = <base address> + <index>

where <base address> is the address of element zero of the array and <index> is the number
of the desired array element.

This calculation can be performed using the 6502 code in Listing 3.

When accessing elements of an integer array, don’t forget that each element requires
two bytes, so the index value must be multiplied by two before adding it to the base address.

The quickest way to double the index is to shift it one position to the left.

This is accomplished with the code in Listing 4.

      ASL I                  ; Multiply the index by two
      ROL I+1                ; before adding it to the base address.
      CLC
      LDA I
      ADC BASE
      STA ARRAYPTR
      LDA I+1
      ADC BASE+1
      STA ARRAYPTR+1
      LDY 40
      LDA (ARRAYPTR),Y       ; Get L.O. byte of array element.
      STA J                  ; Save L.O. byte.
      INY
      LDA (ARRAYPTR),Y       ; Get H.O. byte of array element.
      STA J+1                ; Save H.O. byte.
      LSR I+1                ; Divide the index by two to
      ROR I                  ; set it to its original value.
                 Listing 4.

The ASL instruction shifts the data in memory location I to the left one position.

A zero is shifted into the low-order bit position, and the data in bit number seven is
shifted into the carry flag.

The ROL (rotate left) instruction shifts the high-order byte of I.
The difference between the two instructions is that the contents of the carry flag
(i.e., the bit shifted out of bit seven of the low-order byte) is shifted into the
low-order bit of location I+1.

In many cases it won't matter if you double the value of I.

Sometimes you will reload it anyway.

But in some instances, particularly if I is the index variable of a FOR loop
or some other control variable, you can't leave the value doubled.

To un-double a value (divide it by two) the LSR (logical shift right) and ROR (rotate right)
instructions are used after the calculation to restore the value of I.

Note that the high-order byte of I is shifted to the right before the low order byte.

This is exactly opposite to when the shift left function is used to double the value.

I'll talk more about the shift instructions in a future column;
     
     for now, just duplicate these instructions verbatim.

Multi-Dimensional Arrays
Handling one-dimensional arrays is easy. The formula:

      <adrs> = <base address> + (<index> * <element size>)

(where <element size> is the number of bytes required by each array element)
is completely adequate.

Multiple-dimension arrays are a little more difficult and a lot more time consuming.

For a two-dimensional array, the formula becomes:

      <adrs> = BA + (NDX1 + NDX2 + SIZE1) * WS

where BA is the base address of the array dimensioned as ARRAY(SIZE1, SIZE2] and accessed
as ARRAY[NDX1, NDX2], and each element occupies WS bytes.

The multiplication by WS is easy, assuming a character or integer array.

Shift to the left if integer, and ignore the multiplication by WS if character.

The other multiplication (NDX2+SIZE1), however, cannot be handled with a simple shift
in most cases.

This one requires a real multiply, and multiplies are very slow.

Three-dimensional arrays are even worse—two multiplies are required.

The formula for calculating the address of an element of a three-dimensional array is:

      <adrs> = BA + (NDX1 + (NDX2 + (NDX3 * SIZE2)) * SIZE1) * WS

If you need to use higher-dimensioned arrays, consider using a high level language capable
of supporting your data structure needs.

Speeding Things Up
Normally when you access array elements you go for adjacent elements rather than random
locations within the array.

Once you've calculated the address of an element, obtaining the addresses of adjacent
elements is easy.

If you want to access the next location, simply add WS to the current address.
If you want to access the previous location, subtract WS from the current address.

When dealing with byte (character) arrays, all you need do is increment or decrement
the address by one.

While you could use the ADC instruction sequence to add one to the pointer,
there is a tricky way to add one quickly to a two-byte integer value.

The code to accomplish this is:

      INC VAR
      BNE >0
      INC VAR +1
    ^0:

where VAR is the name of the pointer (or any SPEED/ASM or two-byte integer value)
you wish to increment.

How does it work?

Well, the first increment instruction adds one to the low-order byte of the variable.

Now, the only time overflow occurs when incrementing by one is with the value $FF because
then you wind up with $00.

This is the only time you wind up with zero, and likewise it’s the only time the 6502
zero flag is set after the increment.

Whenever over-flow occurs, you must add one to the high-order byte of the variable,
so the BNE instruction skips the INC VAR+1 instruction.

If the Z flag is set, then the 6502 drops through and increments the high-order byte.

The sequence above only works for character or single-byte arrays.

When dealing with an integer array you need the code:

      CLC
      LDA VAR
      ADC #2
      STA VAR
      BCC >0
      INC VAR+1
    ^0:

I could have incremented VAR twice, but this code is faster and shorter.

Incidentally, the trick here is to recognize that only the carry gets added to VAR+1,
so rather than perform the explicit addition, I only incremented VAR+1 whenever there was
a carry out of the low-order byte.

This trick can be used when adding any 8-bit value to a 16-bit integer variable.

The Indexed-by-X, Indirect Addressing Mode
The indexed-by-X, indirect addressing mode on the 6502 is severely handicapped by
the requirement of zero page.

The way this mode works is, the X register is added to the zero page address that follows
the instruction opcode.

Then this sum points at the low-order byte of a pointer that points to the data to be accessed.

This addressing mode lets you set up a table (or array, if you will) of pointers,
and index into this array of pointers to find the data. Unfortunately, this mode presupposes
lots of zero page at your disposal.

Since this is probably not the case,
      
      the indexed-by-X, indirect addressing mode isn’t very useful.

There is one exception, however.
If you load the X register with zero, then the indexed-by-X, indirect mode degenerates to
a pure indirect addressing mode.

Since this is usually what you're interested in when accessing an element of a
multi-dimensional array, the indexed-by-X, indirect addressing mode may prove useful
on occasion.
